using UnityEngine;
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Threading;
using System.Net;

public class StarSystem : ISerializable
{
	// defining enums
	public enum SolarHeat
	{
		Grey = 1,
		Brown = 2,
		White = 3,
		Green = 4,
		Blue = 5,
		Yellow = 6,
		Orange =7,
		Purple = 8,
		Red = 9
	}


	private Point3D m_Location;
	private SolarHeat m_Heat;
	private int m_StarID;
	private string m_StarName;
	private int m_NumOrbits;
	private Planet.PlanetType m_Type;
	private List<Planet> m_Planets = new List<Planet>();
	private List<int> m_Empty = new List<int>();

	private Hashtable m_SolarColony = new Hashtable();

	private int[] m_ColonyBase = new int[4] { 0, 0, 0, 0 };			// colony base status, {max, unused, developing}
	private List<Kingdom> m_OwnedBy = new List<Kingdom>();			// this solar is owned by which kingdoms, can by multiple kingdoms
	private List<Fleet> m_Dests = new List<Fleet>();						// fleets and dispatchables setting this solar as destination
	private List<Fleet> m_Docking = new List<Fleet>();					// ships docking in this solar
	private List<Fleet> m_Launching = new List<Fleet>();				// ships launching from this solar


	// visual object related
	private GameObject m_Sunlight;

	// ISerializable
	public ulong Serial
	{
		get { return (ulong)m_StarID; }
	}

	// getters and setters
	#region getters and setters
	public Point3D Location
	{
		get { return m_Location; }
		set { m_Location = value; }
	}

	public SolarHeat Heat
	{
		get { return m_Heat; }
		set { m_Heat = value; }
	}

	public int StarID
	{
		get { return m_StarID; }
		set { m_StarID = value; }
	}

	public string StarName
	{
		get { return m_StarName; }
		set { m_StarName = value; }
	}

	public int NumOrbits
	{
		get { return m_NumOrbits; }
		set { m_NumOrbits = value; }
	}

	public Planet.PlanetType Type
	{
		get { return m_Type; }
	}

	public List<Planet> Planets
	{
		get { return m_Planets; }
		set { m_Planets = value; }
	}

	public List<int> Empty
	{
		get { return m_Empty; }
		set { m_Empty = value; }
	}

	public Hashtable SolarColony
	{
		get { return m_SolarColony; }
		set { m_SolarColony = value; }
	}

	public int[] ColonyBase
	{
		get { return m_ColonyBase; }
		set { m_ColonyBase = value; }
	}

	public List<Kingdom> OwnedBy
	{
		get { return m_OwnedBy; }
		set { m_OwnedBy = value; }
	}

	public List<Fleet> Dests
	{
		get { return m_Dests; }
		set { m_Dests = value; }
	}

	public List<Fleet> Docking
	{
		get { return m_Docking; }
		set { m_Docking = value; }
	}

	public List<Fleet> Launching
	{
		get { return m_Launching; }
		set { m_Launching = value; }
	}


	// visual object related
	public GameObject Sunlight
	{
		get { return m_Sunlight; }
		set { m_Sunlight = value; }
	}
	#endregion


	// Constructors of class StarSystem
	public StarSystem()
	{

	}

	public StarSystem(Point3D loc, bool home) : this()
	{
		m_Location = loc;
		m_StarID = ++Galaxy.StarSerial;

		if (m_StarName == null || m_StarName.Length == 0)
			m_StarName = Utility.GetUniqueStarName();

		
		GenerateStar(loc, home);
		FindMaxColonyBase();					// to find maximum number of colonybase allowed
	}

	public StarSystem(Point3D loc, string sname, bool home) : this()
	{
		m_StarName = sname.Trim();
	}


	// sub-classes of class StarSystem
	public class OrbitSorter : IComparer<Planet>
	{
		public int Compare(Planet x, Planet y)
		{
			return Compare((int)x.Orbit, (int)y.Orbit);
		}

		public int Compare(int x, int y)
		{
			return x - y;
		}
	}

	public class KingdomColony
	{
		public Kingdom pKingdom;
		public int pBase;

		public KingdomColony(Kingdom kd)
		{
			pKingdom = kd;
		}
	}


	// ISerializable
	public virtual void Serialize(GenericWriter writer)
	{
		writer.Write((int)0); // version

		writer.Write((Point3D)m_Location);
		writer.Write((int)m_Heat);
		writer.Write((int)m_StarID);
		writer.Write((string)m_StarName);
		writer.Write((int)m_NumOrbits);
		writer.Write((int)m_Type);

		writer.Write((int)m_Planets.Count);

		if (m_Planets.Count > 0)
			for (int i = 0; i < m_Planets.Count; i++)
				writer.Write((Planet)m_Planets[i]);

		writer.Write((int)m_Empty.Count);

		if (m_Empty.Count > 0)
			for (int i = 0; i < m_Empty.Count; i++)
				writer.Write((int)m_Empty[i]);

		writer.Write((int)m_SolarColony.Count);
		foreach (DictionaryEntry de in m_SolarColony)
		{
			writer.Write((Kingdom)de.Key);
			writer.Write((int)de.Value);
		}

		writer.Write((int)m_ColonyBase.Length);
		for (int i = 0; i < m_ColonyBase.Length; i++)
			writer.Write((int)m_ColonyBase[i]);

		writer.Write((int)m_OwnedBy.Count);

		if (m_OwnedBy.Count > 0)
			for (int i = 0; i < m_OwnedBy.Count; i++)
				writer.Write((Kingdom)m_OwnedBy[i]);

		writer.Write((int)m_Dests.Count);

		if (m_Dests.Count > 0)
			for (int i = 0; i < m_Dests.Count; i++)
				writer.Write((Fleet)m_Dests[i]);

		writer.Write((int)m_Docking.Count);

		if (m_Docking.Count > 0)
			for (int i = 0; i < m_Docking.Count; i++)
				writer.Write((Fleet)m_Docking[i]);

		writer.Write((int)m_Launching.Count);

		if (m_Launching.Count > 0)
			for (int i = 0; i < m_Launching.Count; i++)
				writer.Write((Fleet)m_Launching[i]);
	}

	public virtual void Deserialize(GenericReader reader)
	{
		int version = reader.ReadInt();

		m_Location = reader.ReadPoint3D();
		m_Heat = (SolarHeat)reader.ReadInt();
		m_StarID = reader.ReadInt();
		m_StarName = reader.ReadString();
		m_NumOrbits = reader.ReadInt();
		m_Type = (Planet.PlanetType)reader.ReadInt();

		int count = reader.ReadInt();
		m_Planets = new List<Planet>(count);

		if (count > 0)
			for (int i = 0; i < count; i++)
			{
				m_Planets.Add(reader.ReadPlanet());
				m_Planets.Sort(new OrbitSorter());
			}

		count = reader.ReadInt();
		m_Empty = new List<int>(count);

		if (count > 0)
			for (int i = 0; i < count; i++)
				m_Empty.Add(reader.ReadInt());

		m_SolarColony = new Hashtable();

		count = reader.ReadInt();

		if (count > 0)
			for (int i = 0; i < count; i++)
				m_SolarColony.Add(reader.ReadKingdom(), reader.ReadInt());

		count = reader.ReadInt();

		for (int i = 0; i < count; i++)
			m_ColonyBase[i] = reader.ReadInt();

		count = reader.ReadInt();
		m_OwnedBy = new List<Kingdom>();

		if (count > 0)
			for (int i = 0; i < count; i++)
				m_OwnedBy.Add(reader.ReadKingdom());

		count = reader.ReadInt();
		m_Dests = new List<Fleet>();

		if (count > 0)
			for (int i = 0; i < count; i++)
				m_Dests.Add(reader.ReadFleet());

		count = reader.ReadInt();
		m_Docking = new List<Fleet>();

		if (count > 0)
			for (int i = 0; i < count; i++)
				m_Docking.Add(reader.ReadFleet());

		count = reader.ReadInt();
		m_Launching = new List<Fleet>();

		if (count > 0)
			for (int i = 0; i < count; i++)
				m_Launching.Add(reader.ReadFleet());
	}


	// class methods
	public bool GenerateStar(Point3D pt, bool ishome)
	{
		int[] aOrbits;

		int start;
		int empty;          // for empty orbits or gas giants
		bool isGas;

		// to decide the heat level of the star system
		if (!ishome && 0.12 > Utility.RandomDouble())
			m_Heat = Utility.RandomBool() ? SolarHeat.Purple : SolarHeat.Red;
		else if (!ishome && 0.25 > Utility.RandomDouble())
			m_Heat = Utility.RandomBool() ? SolarHeat.Grey : SolarHeat.Brown;
		else
			switch (Utility.RandomMinMax(3, 7))
			{
				case 3: m_Heat = SolarHeat.White; break;
				case 4: m_Heat = SolarHeat.Green; break;
				case 5: m_Heat = SolarHeat.Blue; break;
				case 6: m_Heat = SolarHeat.Yellow; break;
				case 7: m_Heat = SolarHeat.Orange; break;

				default: throw new ArgumentException();
			}

		// to decide how many planets in solar system
		if (m_Heat == SolarHeat.Purple || m_Heat == SolarHeat.Red)
		{
			// extremely hot environment, 2 occupied orbit at most, 1 non-Gas planet at most
			m_NumOrbits = Utility.RandomMinMax(1, 2);

			aOrbits = GetOrbits(5, m_NumOrbits);

			isGas = Utility.RandomBool();
			start = 0;

			if (isGas)
			{
				CreateStarPlanets(aOrbits[0], true, Planet.PlanetType.Extreme);
				start = 1;
			}

			if (!isGas || m_NumOrbits > 1)
				CreateStarPlanets(start, aOrbits, m_NumOrbits, false, Planet.PlanetType.Extreme);

			m_Type = Planet.PlanetType.Extreme;

		}
		else if (m_Heat == SolarHeat.Grey || m_Heat == SolarHeat.Brown)
		{
			// Rather cold enironment, allow at most 3 planets, with at most one gas giant
			m_NumOrbits = Utility.RandomMinMax(1, 3);

			aOrbits = GetOrbits(5, m_NumOrbits);
			isGas = false;

			if (0.25 > Utility.RandomDouble())
			{
				CreateStarPlanets(aOrbits[0], true, Planet.PlanetType.Extreme);         // a Gas Giant
				isGas = true;
			}

			if (isGas)
				start = 1;
			else
				start = 0;

			CreateStarPlanets( start, aOrbits, m_NumOrbits, false, Planet.PlanetType.Poor );

			m_Type = Planet.PlanetType.Poor;
		}
		else
		{
			// for rather normal heat environment
			if (0.125 > Utility.RandomDouble())
				m_NumOrbits = 5;
			else if (!ishome && 0.25 > Utility.RandomDouble())
				m_NumOrbits = Utility.RandomMinMax(1, 2);
			else if (!ishome)
				m_NumOrbits = Utility.RandomMinMax(2, 4);
			else
					m_NumOrbits = Utility.RandomMinMax(2, 5);					// a home solar system

			aOrbits = GetOrbits(5, m_NumOrbits);

			// to decide the number of empty orbits or gas giants
			empty = 0;

			if (m_NumOrbits == 1 || m_NumOrbits == 2)
			{
					if (!ishome && 0.25 > Utility.RandomDouble())
							empty++;
					else if (0.25 > Utility.RandomDouble())
							empty++;
			}
			else if (ishome)
					empty = Utility.Random(m_NumOrbits - 3);
			else
				empty = Utility.Random(m_NumOrbits - 2);

			if (empty > 0)
			{
				for (int i = 0; i < empty; i++)
				{
					if (Utility.RandomBool())
						CreateStarPlanets(aOrbits[i], true, Planet.PlanetType.Extreme);
					else
						m_Empty.Add(aOrbits[i]);
				}
			}

			Planet.PlanetType pltype = (Planet.PlanetType)Utility.RandomMinMax(2, 4);
			CreateStarPlanets(empty, aOrbits, m_NumOrbits, false, pltype);

			m_Type = pltype;
		}

		return true;
	}

	public void FindMaxColonyBase()
	{
		m_ColonyBase[0] = 0;

		foreach (Planet plt in m_Planets)
			if (!plt.Giant && plt.Status == Planet.PlanetStatus.None)
				m_ColonyBase[0] += 1;
	}

	public bool AddNewColony(Kingdom kd, int num)
	{
		if (num > m_ColonyBase[0] || num == 0)
			return false;
		else if (num + m_ColonyBase[1] < 0)
			return false;

		bool found = false;

		if (m_SolarColony.ContainsKey(kd))
		{
			m_SolarColony[kd] = (int)m_SolarColony[kd] + num;
			m_ColonyBase[1] += num;

			if (num > 0)									// only positive value will reduce max, negative value will only be consumed
			{
				m_ColonyBase[0] -= num;
			}

			found = true;
		}

		if (found)
			return true;

		if (num < 0)					// to not create new record if negative value
			return false;

		m_SolarColony.Add(kd, num);
		m_ColonyBase[1] += num;
		m_ColonyBase[0] -= num;

		return true;
	}

	public static int[] GetOrbits(int total, int orbs)
	{
		int[] aorbs = new int[total];
		int orbit;
		int count = 0;

		bool found;

		do
		{
			orbit = Utility.RandomMinMax(1, total);
			found = false;

			foreach (int i in aorbs)
			{
				if (i == orbit)
					found = true;
			}

			if (!found)
			{
				aorbs[count] = orbit;
				count++;
			}

		} while (count < orbs);

		return aorbs;
	}

	// single planet creation
	public bool CreateStarPlanets(int orbit, bool isGas, Planet.PlanetType type)
	{
		CreateStarPlanets(0, new int[] { orbit }, 1, isGas, type);

		return true;
	}

	// overload for multiple planets creation
	public bool CreateStarPlanets(int start, int[] aOrbs, int length, bool isGas, Planet.PlanetType type)
	{
		Planet plnt;

		for (int i = start; i < length; i++)
		{
			plnt = new Planet( new object[] { m_StarID, m_StarName, aOrbs[i], m_Location, isGas, type } );

			plnt.Solar = this;
			m_Planets.Add(plnt);
			m_Planets.Sort(new OrbitSorter());

			Core.Planets.Add(plnt.PlanetID, plnt);
		}

		return true;
	}

	public string GetOwnerText()
	{
		string txt = "";

		foreach (Kingdom kd in m_OwnedBy)
			txt += "|" + kd.Race.RaceType.ToString();

		if (txt.Length > 0)
			txt = ": " + txt.Substring(1);

		return m_StarName + txt;
	}

	public string[] GetFleetTexts(Kingdom kd)
	{
		string[] txt = new string[3] { "", "", "" };
		int[][] ships = new int[3][]
		{
			new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 },
			new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 },
			new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 },
		};

		int[] idx = new int[] { 0, 0, 0 };


		foreach (Fleet fl in m_Docking)
		{
			if (fl.Belong.Home == kd.Home)
			{
				idx[0] = (int)fl.FleetClass;
				ships[0][idx[0]]++;
			}
		}

		foreach (Fleet fl in m_Launching)
		{
			if (fl.Belong.Home == kd.Home)
			{
				if (fl.Location == m_Location)
				{
					idx[1] = (int)fl.FleetClass;
					ships[1][idx[1]]++;
				}
				else
				{
					idx[2] = (int)fl.FleetClass;					// for ships already launched
					ships[2][idx[2]]++;
				}
			}
		}

		for (int i = 7; i >= 0; i--)
			for (int j = 0; j < 3; j++)
				if (ships[j][i] > 0)
					txt[j] += " " + ships[j][i].ToString() + " " + ((Fleet.ShipClass)i).ToString();

		for (int k = 0; k < 3; k++)
			if (txt[k].Length > 0)
				txt[k] = txt[k].Substring(1);

		return txt;
	}

}
